/*
 * ComplexNumber.java
 */

package complexchaos;

import java.util.*;
import java.math.*;
import java.lang.Math;
import java.lang.*;

/**
 * A class to hold complex numbers.
 *
 * @author Don Spickler
 */
public class ComplexNumber implements Cloneable 
{
	private NumberValue real = null;
	private NumberValue imag = null;
	
	/**
     * Class constructor for 2 NumberValue inputs.
     * @param r real part
     * @param i imaginary part
     * @see NumberValue
     */
    public ComplexNumber(final NumberValue r, final NumberValue i)
	{
		real = r;
		imag = i;
	}

    /**
     * Class constructor for 1 NumberValue input.
     * @param r real part (no imaginary part)
     * @see NumberValue
     */
	public ComplexNumber(final NumberValue r)
	{
		real = r;
		imag = NumberValue.makeZero();
	}

    /**
     * Class constructor for setting either entirely real or entierly imaginary
     * @param r number
     * @param realflag whether the r is real (true) or imaginary (false)
     * @see NumberValue
     */
	public ComplexNumber(final NumberValue r, final boolean realflag)
	{
		if (realflag)
		{
			real = r;
			imag = NumberValue.makeZero();
		}
		else
		{
			real = NumberValue.makeZero();
			imag = r;
		}		
	}

    /**
     * Class constructor for 2 long values.
     * @param r real part
     * @param i imaginary part
     */
	public ComplexNumber(final long r, final long i)
	{
		real = new NumberValue(r, 1);
		imag = new NumberValue(i, 1);
	}

    /**
     * Class constructor for rational real and imaginary parts
     * @param rn real part numerator
     * @param rd real part denominator
     * @param in imaginary part numerator
     * @param id imaginary part denominator
     * @see NumberValue
     */
	public ComplexNumber(final long rn, final long rd, final long in, final long id)
	{
		real = new NumberValue(rn, rd);
		imag = new NumberValue(in, id);
	}

    /**
     * Class constructor for 2 double inputs
     * @param r real part
     * @param i imaginary part
     */
	public ComplexNumber(final double r, final double i)
	{
		real = new NumberValue(r);
		imag = new NumberValue(i);
	}

    /**
     * Class default constructor. Makes point equal to 0 + 0i.
     */
	public ComplexNumber()
	{
		real = NumberValue.makeZero();
		imag = NumberValue.makeZero();
	}

    /**
     * Makes the number equal to 0 + 0i.
     * @return 0 + 0i
     */
	public static ComplexNumber makeZero()
	{
		return new ComplexNumber(NumberValue.makeZero(), NumberValue.makeZero());
	}

    /**
     * Makes the number equal to 1 + 0i.
     * @return 1 + 0i
     */
	public static ComplexNumber makeOne()
	{
		return new ComplexNumber(NumberValue.makeOne(), NumberValue.makeZero());
	}

    /**
     * Makes the number equal to -1 + 0i.
     * @return -1 + 0i
     */
	public static ComplexNumber makeMinusOne()
	{
		return new ComplexNumber(NumberValue.makeMinusOne(), NumberValue.makeZero());
	}

    /**
     * Makes the number equal to 0 + i.
     * @return 0 + i
     */
	public static ComplexNumber makeI()
	{
		return new ComplexNumber(NumberValue.makeZero(), NumberValue.makeOne());
	}

    /**
     * Makes the number equal to 0 - i.
     * @return 0 - i
     */
	public static ComplexNumber makeMinusI()
	{
		return new ComplexNumber(NumberValue.makeZero(), NumberValue.makeMinusOne());
	}

    /**
     * Makes the number real by zeroing the imaginary part.
     */
	private void makeReal()
	{
		imag = NumberValue.makeZero();
	}

    /**
     * Gets the real part of the number.
     * @return real part
     */
	public NumberValue getReal()
	{
		return real;
	}

    /**
     * Gets the imaginary part of the number.
     * @return imaginary part
     */
	public NumberValue getImag()
	{
		return imag;
	}

    /**
     * Checks whether the number is real or not.
     * @return whether or not the number is real (true)
     */
	public boolean isReal()
	{
		return imag.isZero();
	}

    /**
     * Checks whether the number is imaginary or not.
     * @return whether or not the number is imaginary (true)
     */
	public boolean isImag()
	{
		return real.isZero();
	}

    /**
     * Checks to see if the number is a real integer.
     * @return true if the real part is an integer and the imaginary part is 0
     */
	public boolean isRealInteger()
	{
		return imag.isZero() && real.isInteger();
	}

    /**
     * Checks to see if the number is equal to 0 + 0i.
     * @return true if the number is equal to 0 + 0i (modulus is 0)
     */
	public boolean isZero()
	{
		if (real.isExact() && imag.isExact())
			return (real.isZero() && imag.isZero());
			
		return (mod() == 0);
	}

    /**
     * Checks to see if the number is equal to 0 + 0i using a specific numerical
     * tolerance.
     * @param tol numerical tolerance
     * @return true if the number is equal to 0 + 0i
     * @see NumericTolerances
     */
	public boolean isZero(NumericTolerances tol)
	{
		if (real.isExact() && imag.isExact())
			return (real.isZero(tol) && imag.isZero(tol));
			
		if (tol.getUseZeroTolerance())
			return (mod() < tol.getZeroTolerance());
		else
			return (mod() == 0);
	}

	public boolean isOne()
	{
		if (real.isExact() && imag.isExact())
			return (real.isOne() && imag.isZero());

		return (this.subtract(ComplexNumber.makeOne()).mod() == 0);
	}

	public boolean isOne(NumericTolerances tol)
	{
		if (real.isExact() && imag.isExact())
			return (real.isOne(tol) && imag.isZero(tol));

		if (tol.getUseEqualityTolerance())
			return (this.subtract(ComplexNumber.makeOne()).mod() < tol.getEqualityTolerance());
		else
			return (this.subtract(ComplexNumber.makeOne()).mod() == 0);
	}

	public boolean isMinusOne()
	{
		if (real.isExact() && imag.isExact())
			return (real.isMinusOne() && imag.isZero());

		return (this.subtract(ComplexNumber.makeMinusOne()).mod() == 0);
	}

	public boolean isMinusOne(NumericTolerances tol)
	{
		if (real.isExact() && imag.isExact())
			return (real.isMinusOne(tol) && imag.isZero(tol));

		if (tol.getUseEqualityTolerance())
			return (this.subtract(ComplexNumber.makeMinusOne()).mod() < tol.getEqualityTolerance());
		else
			return (this.subtract(ComplexNumber.makeMinusOne()).mod() == 0);
	}

	public boolean hasNegativeFirstTerm()
	{
		if (!real.isZero())
			return !real.isPos();
		else if (!imag.isZero())
			return !imag.isPos();
		else
			return false;
	}

	public boolean hasTwoTerms()
	{
		return (!real.isZero()) && (!imag.isZero());
	}

	public ComplexNumber force_approx()
	{
		return new ComplexNumber(new NumberValue(real.approx()), new NumberValue(imag.approx()));
	}	

	public ComplexNumber approx()
	{
		NumberValue newReal;
		NumberValue newImag;
		
		if (real.isInteger())
			newReal = real.clone();
		else
			newReal = new NumberValue(real.approx());
			
		if (imag.isInteger())
			newImag = imag.clone();
		else
			newImag = new NumberValue(imag.approx());
			
		return new ComplexNumber(newReal, newImag);
	}	
	
	public ComplexNumber add(ComplexNumber n)
	{
		ComplexNumber retval = null;
		
		try
		{
			retval = new ComplexNumber(real.add(n.getReal()), imag.add(n.getImag()));
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: add method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber subtract(ComplexNumber n)
	{
		ComplexNumber retval = null;
		
		try
		{
			retval = new ComplexNumber(real.subtract(n.getReal()), imag.subtract(n.getImag()));
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: subtract method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber negate()
	{
		ComplexNumber retval = null;
		
		try
		{
			retval = new ComplexNumber(real.negate(), imag.negate());
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: negate method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber conj()
	{
		ComplexNumber retval = null;
		
		try
		{
			retval = new ComplexNumber(real.clone(), imag.negate());
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: conj method numeric exception.");
		}

		return retval;
	}
	
	public ComplexNumber multiply(ComplexNumber n)
	{
		ComplexNumber retval = null;
		
		try
		{
			retval = new ComplexNumber((real.multiply(n.getReal())).subtract(imag.multiply(n.getImag())), (real.multiply(n.getImag())).add(imag.multiply(n.getReal())));
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: multiply method numeric exception.");
		}

		return retval;
	}
	
	public ComplexNumber divide(ComplexNumber n)
	{
		ComplexNumber retval = null;

		try
		{
			NumberValue den = ((n.getReal()).multiply(n.getReal())).add((n.getImag()).multiply(n.getImag()));
			NumberValue r = (real.multiply(n.getReal())).add(imag.multiply(n.getImag()));
			NumberValue i = (imag.multiply(n.getReal())).subtract(real.multiply(n.getImag()));			
			r = r.divide(den);
			i = i.divide(den);
			retval = new ComplexNumber(r, i);
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: divide method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber divide(double d)
	{
		ComplexNumber retval = null;

		try
		{
			NumberValue den = new NumberValue(d);			
			NumberValue r = real.divide(den);
			NumberValue i = imag.divide(den);
			retval = new ComplexNumber(r, i);
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: divide method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber invert()
	{
		ComplexNumber retval = null;
		
		try
		{
			ComplexNumber one = ComplexNumber.makeOne();
			retval = one.divide(this);
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: invert method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber pow(long p)
	{
		ComplexNumber retval = ComplexNumber.makeOne();
		ComplexNumber base = clone();

		if (isZero())
		{
			if (p == 0)
				return ComplexNumber.makeOne();  //  0^0 = 1, needed for polynomial simplifications.
			else if (p > 0)
				return ComplexNumber.makeZero();
			else if (p < 0)
				throw new ArithmeticException("Division by zero.");
		}
		
		if (isOne())
			return ComplexNumber.makeOne();
		
		if (p < 0)
		{
			base.invert();
			p = -p;
		}

		try
		{
			for (int i = 0; i < p; i++)
				retval = retval.multiply(base);
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: pow(int) method numeric exception.");
		}

		return retval;
	}

	public ComplexNumber pow(ComplexNumber p)
	{
		ComplexNumber retval = new ComplexNumber();
		boolean done = false;
		
		try
		{
			if (p.isReal() && p.real.isExact() && p.real.isInteger())
			{
				retval = this.pow(p.real.getNum().longValue());
				done = true;
			}
			
			if (isReal() && p.isReal() && !done)
				if (p.real.isExact())
				{
					BigInteger pnum = p.real.getNum();
					BigInteger pden = p.real.getDen();
					
					if (real.isPos())
					{						
						retval = new ComplexNumber(Math.pow(real.approx(), p.real.approx()), 0);
						done = true;
					}
					else if (real.isNeg() && (pden.mod(new BigInteger("2")).intValue() == 1))
					{
						retval = new ComplexNumber(Math.pow(-1.0*Math.pow(real.negate().approx(), 1/pden.doubleValue()),pnum.doubleValue()), 0);
						done = true;						
					}
					else if (real.isZero() && p.real.isPos()) // zero
					{
						retval = new ComplexNumber(0, 0);
						done = true;						
					}
				}
			
			if (!done)
				retval = (p.multiply(log())).exp();  //  general complex to complex
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: pow(ComplexNumber) method numeric exception.");
		}
		
		return retval;
	}

	//  Functions

	public ComplexNumber log()   //  log(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = new ComplexNumber(Math.log(mod()), arg());
			if (isReal() && (real.approx() > 0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: log method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber exp()   //  exp(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = new ComplexNumber(Math.exp(real.approx())*Math.cos(imag.approx()), 
																 Math.exp(real.approx())*Math.sin(imag.approx()));
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: exp method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber sin()   //  sin(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{
			retval = (((this.multiply(ComplexNumber.makeI())).exp()).subtract((this.multiply(ComplexNumber.makeMinusI())).exp())).divide(two.multiply(ComplexNumber.makeI()));
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: sin method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber cos()   //  cos(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{
			retval = (((this.multiply(ComplexNumber.makeI())).exp()).add((this.multiply(ComplexNumber.makeMinusI())).exp())).divide(two);
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: cos method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber tan()   //  tan(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (sin()).divide(cos());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: tan method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber cot()   //  cot(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (cos()).divide(sin());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: cot method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber sec()   //  sec(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (ComplexNumber.makeOne()).divide(cos());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: sec method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber csc()   //  csc(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (ComplexNumber.makeOne()).divide(sin());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: csc method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber sinh()   //  sinh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{
			retval = ((this.exp()).subtract((this.multiply(ComplexNumber.makeMinusOne())).exp())).divide(two);
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: sinh method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber cosh()   //  cosh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{
			retval = ((this.exp()).add((this.multiply(ComplexNumber.makeMinusOne())).exp())).divide(two);
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: cosh method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber tanh()   //  tanh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (sinh()).divide(cosh());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: tanh method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber coth()   //  coth(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (cosh()).divide(sinh());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: coth method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber sech()   //  sech(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (ComplexNumber.makeOne()).divide(cosh());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: sech method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber csch()   //  csch(this)
	{
		ComplexNumber retval = new ComplexNumber();
		
		try
		{
			retval = (ComplexNumber.makeOne()).divide(sinh());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: csch method numeric exception.");
		}
		
		return retval;
	}
	
	public ComplexNumber asin()   //  asin(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			ComplexNumber iz = (ComplexNumber.makeI()).multiply(this);
			ComplexNumber one_z2 = (ComplexNumber.makeOne()).subtract(this.pow(2));
			ComplexNumber sqrt_one_z2 = one_z2.pow(half);
			
			retval = (ComplexNumber.makeMinusI()).multiply((iz.add(sqrt_one_z2)).log());
			if (isReal() && (Math.abs(real.approx()) < 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: asin method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acos()   //  acos(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			ComplexNumber one_z2 = (ComplexNumber.makeOne()).subtract(this.pow(2));
			ComplexNumber sqrt_one_z2 = one_z2.pow(half);
			ComplexNumber i_sqrt_one_z2 = (ComplexNumber.makeI()).multiply(sqrt_one_z2);
			
			retval = (ComplexNumber.makeMinusI()).multiply((this.add(i_sqrt_one_z2)).log());
			if (isReal() && (Math.abs(real.approx()) < 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acos method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber atan()   //  atan(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{			
			//  1/(2*I)*log((1+I*z)/(1-I*z))
			ComplexNumber i_2 = (ComplexNumber.makeOne()).divide((ComplexNumber.makeI()).multiply(two));
			ComplexNumber one_p_iz = (ComplexNumber.makeOne()).add(this.multiply(ComplexNumber.makeI()));
			ComplexNumber one_m_iz = (ComplexNumber.makeOne()).subtract(this.multiply(ComplexNumber.makeI()));
			
			retval = i_2.multiply((one_p_iz.divide(one_m_iz)).log());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: atan method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acot()   //  acot(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		ComplexNumber two = new ComplexNumber(new NumberValue("2", "1"));
		
		try
		{			
			//  1/(2*I)*log((z+i)/(z-i))
			ComplexNumber i_2 = (ComplexNumber.makeOne()).divide((ComplexNumber.makeI()).multiply(two));
			ComplexNumber z_p_i = this.add(ComplexNumber.makeI());
			ComplexNumber z_m_i = this.subtract(ComplexNumber.makeI());
			
			retval = i_2.multiply((z_p_i.divide(z_m_i)).log());
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acot method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber asec()   //  asec(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			//  1/I*log((1-(1-z^2)^(1/2))/z)
			ComplexNumber inv_i = (ComplexNumber.makeOne()).divide(ComplexNumber.makeI());
			ComplexNumber numz = (ComplexNumber.makeOne()).subtract(((ComplexNumber.makeOne()).subtract(this.pow(2))).pow(half));
			ComplexNumber denz = this;
			
			retval = inv_i.multiply((numz.divide(denz)).log());
			if (isReal() && (Math.abs(real.approx()) > 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: asec method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acsc()  //  acsc(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			//  1/I*log((I+(z^2-1)^(1/2))/z)  
			ComplexNumber inv_i = (ComplexNumber.makeOne()).divide(ComplexNumber.makeI());
			ComplexNumber numz = (ComplexNumber.makeI()).add(((this.pow(2)).subtract(ComplexNumber.makeOne())).pow(half));
			ComplexNumber denz = this;
			
			retval = inv_i.multiply((numz.divide(denz)).log());
			if (isReal() && (Math.abs(real.approx()) > 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acsc method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber asinh()   //  asinh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = (this.add(((this.pow(2)).add(ComplexNumber.makeOne())).pow(half))).log();
			if (isReal()) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: asinh method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acosh()   //  acosh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = (this.add(((this.pow(2)).subtract(ComplexNumber.makeOne())).pow(half))).log();
			if (isReal() && (Math.abs(real.approx()) > 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acosh method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber atanh()   //  atanh(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = half.multiply((((ComplexNumber.makeOne()).add(this)).divide((ComplexNumber.makeOne()).subtract(this))).log());
			if (isReal() && (Math.abs(real.approx()) < 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: atanh method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acoth()   //  acoth(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = half.multiply((((ComplexNumber.makeOne()).add(this)).divide(this.subtract(ComplexNumber.makeOne()))).log());
			if (isReal() && (Math.abs(real.approx()) > 1.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acoth method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber asech()   //  asech(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = (((ComplexNumber.makeOne()).add(((ComplexNumber.makeOne()).subtract(this.pow(2))).pow(half))).divide(this)).log();
			if (isReal() && (Math.abs(real.approx()) < 1.0) && (Math.abs(real.approx()) > 0.0)) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: asech method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber acsch()   //  acsch(this)
	{
		ComplexNumber retval = new ComplexNumber();
		ComplexNumber half = new ComplexNumber(new NumberValue("1", "2"));
		
		try
		{			
			retval = (((ComplexNumber.makeOne()).add(((ComplexNumber.makeOne()).add(this.pow(2))).pow(half))).divide(this)).log();
			if (isReal() && (!real.isZero())) retval.makeReal();
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: acsch method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber sign()   //  sign(this)  For real values.
	{
		ComplexNumber retval = new ComplexNumber();

		try
		{	
			if (real.isZero())		
				retval = ComplexNumber.makeZero();
			else if (real.approx() > 0)
				retval = ComplexNumber.makeOne();
			else
				retval = ComplexNumber.makeMinusOne();			
		} catch (Exception e)
		{
			throw new ArithmeticException("ComplexNumber: sign method numeric exception.");
		}
		
		return retval;
	}

	public ComplexNumber zeron()   //  zeron(this)
	{
		ComplexNumber retval = ComplexNumber.makeZero();
		
		if (isZero())
			throw new ArithmeticException("ComplexNumber: zeron method numeric exception.");
		
		return retval;
	}
	
	//public synchronized Object clone()
	public synchronized ComplexNumber clone()
	{
		return new ComplexNumber(real.clone(), imag.clone());
	}	

	public String toString()
	{
		String retstr = "";

		if (!real.isZero())
			retstr += real.toString();
			
		if (!imag.isZero())
			if (!real.isZero())
			{
				if (imag.approx() < 0)
				{
					if (imag.negate().isOne())
						retstr += " - i";
					else
						retstr += " - " + imag.negate().toString() + "i";
				}
				else
				{
					if (imag.isOne())
						retstr += " + i";
					else
						retstr += " + " + imag.toString() + "i";
				}
			}
			else
			{
				if (imag.isOne())
					retstr += "i";
				else if (imag.negate().isOne())
					retstr += "-i";
				else
					retstr += imag.toString() + "i";
			}

		if (real.isZero() && imag.isZero())
			retstr = "0";

		return retstr;
	}

	public String toLaTeXString()
	{
		String retstr = "";
		
		if (!real.isZero())
			retstr += real.toLaTeXString();
			
		if (!imag.isZero())
			if (!real.isZero())
			{
				if (imag.approx() < 0)
				{
					if (imag.negate().isOne())
						retstr += " - i";
					else
						retstr += " - " + imag.negate().toLaTeXString() + "i";
				}
				else
				{
					if (imag.isOne())
						retstr += " + i";
					else
						retstr += " + " + imag.toLaTeXString() + "i";
				}
			}
			else
			{
				if (imag.isOne())
					retstr += "i";
				else if (imag.negate().isOne())
					retstr += "-i";
				else
					retstr += imag.toLaTeXString() + "i";
			}

		if (real.isZero() && imag.isZero())
			retstr = "0";

		return retstr;
	}

	public String toCodeString()
	{
		String retstr = "";
		
		if (!real.isZero())
			retstr += real.toString();
			
		if (!imag.isZero())
			if (!real.isZero())
			{
				if (imag.approx() < 0)
				{
					if (imag.negate().isOne())
						retstr += " - i";
					else
						retstr += " - " + imag.negate().toString() + "*i";
				}
				else
				{
					if (imag.isOne())
						retstr += " + i";
					else
						retstr += " + " + imag.toString() + "*i";
				}
			}
			else
			{
				if (imag.isOne())
					retstr += "i";
				else if (imag.negate().isOne())
					retstr += "-i";
				else
					retstr += imag.toString() + "*i";
			}

		if (real.isZero() && imag.isZero())
			retstr = "0";

		return retstr;
	}

	public String toMapleCodeString()
	{
		String retstr = "";
		
		if (!real.isZero())
			retstr += real.toString();
			
		if (!imag.isZero())
			if (!real.isZero())
			{
				if (imag.approx() < 0)
				{
					if (imag.negate().isOne())
						retstr += " - I";
					else
						retstr += " - " + imag.negate().toString() + "*I";
				}
				else
				{
					if (imag.isOne())
						retstr += " + I";
					else
						retstr += " + " + imag.toString() + "*I";
				}
			}
			else
			{
				if (imag.isOne())
					retstr += "i";
				else if (imag.negate().isOne())
					retstr += "-I";
				else
					retstr += imag.toString() + "*I";
			}

		if (real.isZero() && imag.isZero())
			retstr = "0";

		return retstr;
	}

	public double mod()
	{
		return Math.sqrt(real.approx()*real.approx()+imag.approx()*imag.approx());
	}

	public double arg()
	{
		return Math.atan2(imag.approx(), real.approx());
	}

	public boolean equals(ComplexNumber c)
	{
		return (real.equals(c.getReal()) && imag.equals(c.getImag()));
	}

	public ComplexNumber adjustToZero(NumericTolerances tol)
	{
		if (isZero(tol))
			return ComplexNumber.makeZero();
		else
			return this.clone();
	}

	public ComplexNumber adjustComponentsToZero(NumericTolerances tol)
	{
		NumberValue r = getReal();
		NumberValue i = getImag();
		
		if (r.isZero(tol))
			r = NumberValue.makeZero();
			
		if (i.isZero(tol))
			i = NumberValue.makeZero();

		return new ComplexNumber(r, i);	
	}

	public ComplexNumber adjust(NumericTolerances tol)
	{
		if (isZero(tol))
			return ComplexNumber.makeZero();
		if (isOne(tol))
			return ComplexNumber.makeOne();
		if (isMinusOne(tol))
			return ComplexNumber.makeMinusOne();
		else
			return this.clone();
	}

	public static ComplexNumber factorial(long n)
	{
		return new ComplexNumber(NumberValue.factorial(n), NumberValue.makeZero());
	}

}